package com.fly.snailrun.business;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fly.snailrun.base.config.JianYingConfig;
import com.fly.snailrun.base.constant.JianYinConstant;
import com.fly.snailrun.base.exception.BusinessErrorCodeEnum;
import com.fly.snailrun.base.exception.BusinessException;
import com.fly.snailrun.domain.dto.*;
import com.fly.snailrun.utils.JianYinUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class DraftContentHandlerService {
    private static Logger log = LoggerFactory.getLogger(DraftContentHandlerService.class);

    /**
     * 物料使用频次
     */
    private static final String MATERIAL_USE_RECORD = "materialUseRecord.json";

    /**
     * 剪映草稿的json文件
     */
    private JSONObject draftContent;

    private Map<String, Integer> materialUseRecordMap;

    @Autowired
    private JianYingConfig jianYingConfig;

    private String recordFilePath = Paths.get("src/main/resources", MATERIAL_USE_RECORD).toAbsolutePath().toString();

    @PostConstruct
    public void init() {
        this.draftContent = JianYinUtil.getDraftContent();
        materialUseRecordMap = getMaterialUseRecord(recordFilePath);
    }

    public Map<String, Integer> getMaterialUseRecord(String fileName) {
        File file = FileUtil.newFile(fileName);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                log.error("DraftContentHandlerService getMaterialUseRecord is error:{}", e);
                throw new BusinessException(BusinessErrorCodeEnum.SERVER_COMMON_ERROR);
            }
        }
        String read = FileUtil.readUtf8String(file);
        return ObjectUtil.defaultIfNull(JSONObject.parseObject(read, Map.class), new HashMap<>());
    }

    /**
     * 物料不限制使用次数
     */
    public void start() {
        //读取改目录下的所有文件
        List<String> filePath = JianYinUtil.getAllFiles();
        //打乱顺序
        Collections.shuffle(filePath);
        for (String item : filePath) {
            Videos videos = appendVideo(item);
            appendMainSegment(videos);
            //随机变速
            turnSpeed();
            //多次分割
            split();
            if (getDraftTime() < getMainSegmentTime()) {
                break;
            }
        }
        //输出
        flush();
    }

    /**
     * 增加物料使用频次的限制
     *
     * @param draftFileName
     */
    public void createDraftMovieNoConfig(String draftFileName) {
        draftContent = JianYinUtil.getDraftContent(draftFileName);
        // 获取所有文件
        List<String> fileNameList = JianYinUtil.getAllFiles();
        fileNameList.forEach(fileName -> {
            materialUseRecordMap.putIfAbsent(fileName, 0);
        });
        //
        Map<String, Integer> fileMap = fileNameList.stream().
                filter(path -> !getSegmentVideoNames().contains(path)).
                collect(Collectors.toMap(Function.identity(), materialUseRecordMap::get));

        Map<Integer, List<String>> valueOfMap = fileMap.entrySet()
                .stream()
                .collect(Collectors.groupingBy(Map.Entry::getValue,
                        Collectors.mapping(Map.Entry::getKey, Collectors.toList()))
                );
        //打乱
        List<String> sortFile = new ArrayList<>();
        MapUtil.sort(valueOfMap).forEach((k, v) -> {
            Collections.shuffle(v);
            sortFile.addAll(v);
        });
        for (String item : sortFile) {
            //防重复
            List<String> existsFiles = getMainSegmentMaterialNames();
            if (existsFiles.contains(item)) {
                continue;
            }
            Videos videos = appendVideo(item);
            appendMainSegment(videos);
            turnSpeed();
            split();
            materialUseRecordMap.put(item, materialUseRecordMap.getOrDefault(item, 0) + 1);
            if (getDraftTime() < getMainSegmentTime()) {
                break;
            }
        }
        FileUtil.writeUtf8String(JSONObject.toJSONString(materialUseRecordMap), recordFilePath);
        flush(draftFileName);
    }

    public List<String> getMainSegmentMaterialNames() {
        JSONArray mainSegments = getMainSegments();
        JSONArray videos = getVideos();
        List<String> fileNames = new ArrayList<>();
        videos.forEach(v -> {
            JSONObject video = (JSONObject) v;
            String fileName = video.getString("material_name");
            fileNames.add(fileName);
        });
        return fileNames;
    }

    /**
     * 找到主轨道已添加的视频名称
     *
     * @return
     */
    public List<String> getSegmentVideoNames() {
        List<String> segmentVideoNameList = getSegmentVideoId().stream().distinct()
                .map(this::getVideoPathById)
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toList());
        return segmentVideoNameList;
    }

    public List<String> getSegmentVideoId() {
        List<String> list = new ArrayList<>();
        JSONArray mainSegments = getMainSegments();
        for (Object segment : mainSegments) {
            JSONObject s = (JSONObject) segment;
            list.add(s.getString("material_id"));
        }
        return list;
    }

    public String getVideoPathById(String videoId) {
        JSONArray videos = getVideos();
        for (Object video : videos) {
            if ((StrUtil.equals(videoId, ((JSONObject) video).getString("id")))) {
                return ((JSONObject) video).getString("path");
            }
        }
        return null;
    }

    public void createDraftMovieByConfig(DraftFileGenerateDto draftFileGenerateDto, String draftFileName) {
        if (Objects.isNull(draftFileGenerateDto)) {
            log.info("DraftContentHandlerServiceHandlerService createDraftMovieByConfig draftFileGenerateDto is null");
            this.createDraftMovieNoConfig(draftFileName);
            return;
        }
        draftContent = JianYinUtil.getDraftContent(draftFileName);
        List<String> filePathList = JianYinUtil.getAllFiles();
        Collections.shuffle(filePathList);
        for (String fileName : filePathList) {
            appendVideo(fileName, draftFileGenerateDto.getVideoMin(), draftFileGenerateDto.getVideoMax());
            turnSpeed(draftFileGenerateDto.getSpeedMin(), draftFileGenerateDto.getSpeedMax());
            split(draftFileGenerateDto.getBlurMin(), draftFileGenerateDto.getBlurMax(), draftFileGenerateDto.getSplitMin(), draftFileGenerateDto.getSplitMax());
            break;
        }
        flush(draftFileName);
    }

    public Videos appendVideo(String path) {
        Integer videoRandomRange = JianYinUtil.getRandomRange();
        VideoInfo info = new VideoInfo(path);
        Videos video = new Videos();
        video.setPath(path);
        video.setId(JianYinUtil.getId());
        video.setWidth(info.getWidth());
        video.setHeight(info.getHeight());
        video.setDuration(info.getDuration() * videoRandomRange / 100);
        video.setType(JianYinConstant.VIDEO);
        JSONObject materials = draftContent.getJSONObject(JianYinConstant.MATERIALS);
        JSONArray videos = materials.getJSONArray(JianYinConstant.VIDEOS);
        JSONObject appendVideo = JSONObject.parseObject(JSONObject.toJSONString(video));
        videos.add(appendVideo);
        materials.put(JianYinConstant.VIDEOS, videos);
        draftContent.put(JianYinConstant.MATERIALS, materials);
        return video;
    }

    public Videos appendVideo(String path, Integer videoMin, Integer videoMax) {
        Integer randomRange = JianYinUtil.getRandomRange(videoMin, videoMax);
        VideoInfo info = new VideoInfo(path);
        Videos video = new Videos();
        video.setPath(path);
        video.setId(JianYinUtil.getId());
        video.setWidth(info.getWidth());
        video.setHeight(info.getHeight());
        video.setDuration(info.getDuration() * randomRange / 100);
        video.setType(JianYinConstant.VIDEO);
        JSONObject materials = draftContent.getJSONObject(JianYinConstant.MATERIALS);
        JSONArray videos = materials.getJSONArray(JianYinConstant.VIDEOS);
        JSONObject appendVideo = JSONObject.parseObject(JSONObject.toJSONString(video));
        videos.add(appendVideo);
        materials.put(JianYinConstant.VIDEOS, videos);
        draftContent.put(JianYinConstant.MATERIALS, materials);
        return video;
    }

    //将视频追加到主轨道
    public void appendMainSegment(Videos video) {
        JSONArray mainSegments = getMainSegments();
        JSONObject lastSegment = (JSONObject) mainSegments.get(mainSegments.size() - 1);
        JSONObject lastTargetTimeRange = lastSegment.getJSONObject(JianYinConstant.TARGET_TIME_RANGE);
        Long lastDuration = lastTargetTimeRange.getLong(JianYinConstant.DURATION);
        Long lastStart = lastTargetTimeRange.getLong(JianYinConstant.START);

        RangeBase sourceTimeRange = new RangeBase();
        sourceTimeRange.setStart(0L);
        sourceTimeRange.setDuration(video.getDuration());

        RangeBase targetTimeRange = new RangeBase();
        targetTimeRange.setDuration(video.getDuration());
        targetTimeRange.setStart(lastStart + lastDuration);

        //模拟第一个片段的行为
        JSONObject firstSegment = mainSegments.getJSONObject(0);
        JSONObject clip = firstSegment.getJSONObject("clip");

        Segment segment = new Segment();
        segment.setMaterialId(video.getId());
        segment.setSourceTimeRange(sourceTimeRange);
        segment.setTargetTimeRange(targetTimeRange);
        segment.setMaterialId(video.getId());
        segment.setExtraMaterialRefs(new ArrayList<>());
        //TODO
        JSONObject seg = JSONObject.parseObject(JSONObject.toJSONString(segment));
        seg.put("clip", JSONObject.parseObject(JSONObject.toJSONString(clip)));
        mainSegments.add(seg);
    }

    //随机切割最后一个片段,切割到不能切割为止
    public void split() {
        JSONArray mainSegments1 = getMainSegments();
        JSONObject lastSegment1 = (JSONObject) mainSegments1.get(mainSegments1.size() - 1);
        Double speed = lastSegment1.getDouble(JianYinConstant.SPEED);
        Integer blur = JianYinUtil.getRandomBlur();
        //原视频的时长
        while (true) {
            //倒数第一个segment && video
            JSONArray mainSegments = getMainSegments();
            JSONArray videos = getVideos();
            JSONObject lastSegment = (JSONObject) mainSegments.get(mainSegments.size() - 1);
            JSONObject lastVideo = (JSONObject) videos.get(videos.size() - 1);

            //倒数第一个segment的时长
            RangeBase lastSegmentRanger = lastSegment.getObject(JianYinConstant.TARGET_TIME_RANGE, RangeBase.class);
            JSONObject startSegmentTimeRange = lastSegment.getJSONObject(JianYinConstant.SOURCE_TIME_RANGE);

            Long sourceStart = startSegmentTimeRange.getLong(JianYinConstant.START);
            Long videoDuration = lastVideo.getLong(JianYinConstant.DURATION);
            Long duration = lastSegmentRanger.getDuration();
            JSONObject jsonObject = lastSegment.getJSONObject(JianYinConstant.TARGET_TIME_RANGE);
            JSONArray canvasesList = getCanvases();

            //处理画布
            Canvases canvases = appendCanvases(blur);
            canvasesList.add(JSONObject.parseObject(canvases.toString()));
            JSONArray materialRefs = lastSegment.getJSONArray("extra_material_refs");
            materialRefs.add(canvases.getId());

            //处理位置
            JSONObject clip = lastSegment.getJSONObject("clip");
            if (jianYingConfig.getEnableLocation()) {
                JSONObject transform = clip.getJSONObject("transform");
                transform.put("x", JianYinUtil.getRandomXLocation());
                transform.put("y", JianYinUtil.getRandomYLocation());
            }
            //处理缩放
            if (jianYingConfig.getEnableScale()) {
                JSONObject scale = clip.getJSONObject("scale");
                double scaleValue = JianYinUtil.getRandomScale();
                log.info("scaleValue:{}", scaleValue);
                scale.put("x", scaleValue / 100);
                scale.put("y", scaleValue / 100);
            }
            lastSegment.put("clip", clip);

            Long randomSplit = JianYinUtil.getRandomSplit();
            if (videoDuration - sourceStart <= randomSplit) {
                //随机切割素材已经是最小,不能在进行切割
                //jsonObject.put(JianYinConstant.DURATION, duration / speed);
                break;
            }
            JSONObject cloneVideo = (JSONObject) lastVideo.clone();
            String videoId = JianYinUtil.getId();
            cloneVideo.put("id", videoId);
            videos.add(cloneVideo);

            //开始切割
            jsonObject.put(JianYinConstant.DURATION, randomSplit / speed);
            startSegmentTimeRange.put(JianYinConstant.DURATION, randomSplit);

            //拼接尾部
            JSONObject cloneSegment = JSONObject.parseObject(lastSegment.toString());
            Long start = lastSegmentRanger.getStart();
            long end = start + Math.round(randomSplit / speed);
            long splitDuration = duration - randomSplit;
            cloneSegment.put(JianYinConstant.MATERIAL_ID, videoId);
            cloneSegment.put("id", JianYinUtil.getId());
            cloneSegment.put("extra_material_refs", new ArrayList<>());
            RangeBase targetRangeBase = new RangeBase();
            targetRangeBase.setStart(end);
            targetRangeBase.setDuration(splitDuration);
            cloneSegment.put(JianYinConstant.TARGET_TIME_RANGE, JSONObject.toJSON(targetRangeBase));
            RangeBase sourceRangeBase = new RangeBase();
            sourceRangeBase.setDuration(duration);
            sourceRangeBase.setStart(sourceStart + randomSplit);
            cloneSegment.put(JianYinConstant.SOURCE_TIME_RANGE, JSONObject.toJSON(sourceRangeBase));
            mainSegments.add(cloneSegment);
            //切割完成
        }
    }

    //随机切割最后一个片段,切割到不能切割为止
    public void split(Integer blurMin, Integer blurMax, Integer splitMin, Integer splitMax) {
        JSONArray mainSegments1 = getMainSegments();
        JSONObject lastSegment1 = (JSONObject) mainSegments1.get(mainSegments1.size() - 1);
        Double speed = lastSegment1.getDouble(JianYinConstant.SPEED);
        Integer blur = JianYinUtil.getRandomBlur(blurMin, blurMax);
        //原视频的时长
        while (true) {
            //倒数第一个segment && video
            JSONArray mainSegments = getMainSegments();
            JSONArray videos = getVideos();
            JSONObject lastSegment = (JSONObject) mainSegments.get(mainSegments.size() - 1);
            JSONObject lastVideo = (JSONObject) videos.get(videos.size() - 1);


            JSONObject cloneSegment = (JSONObject) lastSegment.clone();
            RangeBase lastSegmentRanger = lastSegment.getObject(JianYinConstant.TARGET_TIME_RANGE, RangeBase.class);
            JSONObject startSegmentTimeRange = lastSegment.getJSONObject(JianYinConstant.SOURCE_TIME_RANGE);

            Long sourceStart = startSegmentTimeRange.getLong(JianYinConstant.START);
            Long videoDuration = lastVideo.getLong(JianYinConstant.DURATION);
            Long duration = lastSegmentRanger.getDuration();
            JSONObject jsonObject = lastSegment.getJSONObject(JianYinConstant.TARGET_TIME_RANGE);

            //处理画布
            JSONArray canvasesList = getCanvases();
            Canvases canvases = appendCanvases(blur);
            canvasesList.add(JSONObject.parseObject(canvases.toString()));
            JSONArray materialRefs = lastSegment.getJSONArray("extra_material_refs");
            materialRefs.add(canvases.getId());

            Long randomSplit = JianYinUtil.getRandomSplit(splitMin, splitMax);
            //随机切割素材已经是最小,不能在进行切割
            if (videoDuration - sourceStart <= randomSplit) {
                //jsonObject.put(JianYinConstant.DURATION, duration / speed);
                break;
            }
            JSONObject cloneVideo = (JSONObject) lastVideo.clone();
            String videoId = JianYinUtil.getId();
            cloneVideo.put("id", videoId);
            videos.add(cloneVideo);
            //开始切割
            jsonObject.put(JianYinConstant.DURATION, randomSplit / speed);
            startSegmentTimeRange.put(JianYinConstant.DURATION, randomSplit);
            //拼接尾部
            Long start = lastSegmentRanger.getStart();
            long end = start + Math.round(randomSplit / speed);
            long splitDuration = duration - randomSplit;
            cloneSegment.put(JianYinConstant.MATERIAL_ID, videoId);
            cloneSegment.put("id", JianYinUtil.getId());
            cloneSegment.put("extra_material_refs", new ArrayList<>());
            RangeBase targetRangeBase = new RangeBase();
            targetRangeBase.setStart(end);
            targetRangeBase.setDuration(splitDuration);
            cloneSegment.put(JianYinConstant.TARGET_TIME_RANGE, JSONObject.toJSON(targetRangeBase));
            RangeBase sourceRangeBase = new RangeBase();
            sourceRangeBase.setDuration(duration);
            sourceRangeBase.setStart(sourceStart + randomSplit);
            cloneSegment.put(JianYinConstant.SOURCE_TIME_RANGE, JSONObject.toJSON(sourceRangeBase));
            mainSegments.add(cloneSegment);
            //切割完成
        }
    }

    /**
     * 设置变速值
     */
    public void turnSpeed() {
        Double randomSpeed = JianYinUtil.getRandomSpeed();
        JSONArray mainSegments = getMainSegments();
        JSONObject lastSegment = (JSONObject) mainSegments.get(mainSegments.size() - 1);
        lastSegment.put("speed", randomSpeed);
        log.info("变速: speed:{}", randomSpeed);
    }

    public void turnSpeed(Double speedMin, Double speedMax) {
        JSONObject materials = draftContent.getJSONObject(JianYinConstant.MATERIALS);
        Double randomSpeed = JianYinUtil.getRandomSpeed(speedMin, speedMax);
        JSONArray mainSegments = getMainSegments();
        JSONObject lastSegment = (JSONObject) mainSegments.get(mainSegments.size() - 1);
        lastSegment.put("speed", randomSpeed);
        log.info("变速: speed:{}", randomSpeed);
    }


    public JSONArray getVideos() {
        JSONObject materials = draftContent.getJSONObject(JianYinConstant.MATERIALS);
        return materials.getJSONArray(JianYinConstant.VIDEOS);
    }


    public JSONArray getTracks() {
        return draftContent.getJSONArray(JianYinConstant.TRACKS);
    }

    public JSONObject getMainTrack() {
        JSONArray tracks = getTracks();
        for (Object track : tracks) {
            JSONObject track1 = (JSONObject) track;
            int flag = track1.getIntValue(JianYinConstant.FLAG);
            if (0 == flag) {
                return track1;
            }
        }
        return null;
    }

    public JSONArray getMainSegments() {
        JSONObject mainTrack = getMainTrack();
        return mainTrack.getJSONArray(JianYinConstant.SEGMENTS);
    }

    public JSONArray getCanvases() {
        JSONObject materials = draftContent.getJSONObject(JianYinConstant.MATERIALS);
        return materials.getJSONArray("canvases");
    }

    /**
     * 获取主轨道时间
     *
     * @return
     */
    public Long getMainSegmentTime() {
        JSONArray mainSegments = getMainSegments();
        JSONObject lastSegment = (JSONObject) mainSegments.get(mainSegments.size() - 1);
        JSONObject targetTimeRange = lastSegment.getJSONObject(JianYinConstant.TARGET_TIME_RANGE);
        Long duration = targetTimeRange.getLong(JianYinConstant.DURATION);
        Long start = targetTimeRange.getLong(JianYinConstant.START);
        return duration + start;
    }


    public Long getDraftTime() {
        return draftContent.getLong(JianYinConstant.DURATION);
    }

    /**
     * 追加画布 背景
     *
     * @return
     */
    public Canvases appendCanvases(Integer blur) {
        String id = JianYinUtil.getId();
        Canvases canvases = new Canvases(id, blur);
        JSONObject materials = draftContent.getJSONObject("materials");
        JSONArray canvasesList = materials.getJSONArray("canvases");
        canvasesList.add(JSONObject.parseObject(canvases.toString()));
        return canvases;
    }

    public void flush() {
        FileUtil.writeUtf8String(JSONObject.toJSONString(draftContent, SerializerFeature.DisableCircularReferenceDetect), JianYinUtil.getDraftContentPath());
    }

    public void flush(String draftName) {
        FileUtil.writeUtf8String(JSONObject.toJSONString(draftContent, SerializerFeature.DisableCircularReferenceDetect), JianYinUtil.getDraftContentPath(draftName));
    }
}
